/*****************************************************************************/
function ModelFactory(site,Model,Dumper)
{
	this.site=site;
	this.Dumper=Dumper;
	this.Model=Model;
	return this;
}

/*****************************************************************************/
ModelFactory.prototype.mk_where=function(model,rule,hier,pos)
{
	var href={
		BIND_ADD: [],
		FOOTER: "",
		FOOTER2: "",
		REPLACE: { where: ""}
	};
	if (hier) {
		href.BIND_ADD.push(hier.root_id);
		href.REPLACE.where+=" and\n	coalesce(main.parent_id,0)=$"+pos;pos++;
	}
	for (var k in rule) {
		switch (k) {
			case "order_by":
				href.FOOTER+="\norder by "+rule.order_by;
				continue;
			case "limit":
				href.FOOTER2+="\nlimit "+rule.limit;
				continue;
			case "offset":
				href.FOOTER2+="\noffset "+rule.offset;
				continue;
		}
		if (rule[k]==null) {
			href.REPLACE.where+=" and\n	main."+k+" is null";
			continue;
		}
		switch (typeof rule[k]) {
			case "string":
			case "number":
				href.BIND_ADD.push(rule[k]);
				href.REPLACE.where+=" and\n	main."+k+"=$"+pos;pos++;
				break;
			case "object":
				switch(rule[k].type) {
					case "=":
					case "<>":
					case "<":
					case ">":
					case "<=":
					case ">=":
						if (rule[k].asis) {
							href.REPLACE.where+=" and\n	main."+k+rule[k].type+rule[k].value;
						} else {
							href.BIND_ADD.push(rule[k].value);
							href.REPLACE.where+=" and\n	main."+k+rule[k].type;
							switch (rule[k].format) {
								case "date":
									href.REPLACE.where+="to_date($"+pos+",'DD.MM.YYYY')";
									break;
								default:
									href.REPLACE.where+="$"+pos;
							}
							pos++;
						}
						break;
					case "like":
						href.BIND_ADD.push(rule[k].value);
						href.REPLACE.where+=" and\n	main."+k+" like "+"$"+pos;pos++;
						break;
					case "like-lower":
						href.BIND_ADD.push(rule[k].value);
						href.REPLACE.where+=" and\n	lower(main."+k+") like "+"$"+pos;pos++;
						break;
					case "between":
						href.BIND_ADD.push(rule[k].value1,rule[k].value2);
						switch (rule[k].format) {
							case "date":
								href.REPLACE.where+=" and\n	main."+k+" between to_date("+"$"+pos+",'DD.MM.YYYY') and to_date($"+(pos+1)+",'DD.MM.YYYY')";pos+=2;
								break;
							default:
								href.REPLACE.where+=" and\n	main."+k+" between "+"$"+pos+" and $"+(pos+1);pos+=2;
								break;
						}
						break;
					case "in": // Potentially unsafe
					case "not in": // Potentially unsafe
						href.REPLACE.where+=" and";
						if (rule[k].or_null) href.REPLACE.where+="(";
						href.REPLACE.where+="\n";
						if (rule[k].or_null) href.REPLACE.where+="	main."+k+" is null or\n";
						if (rule[k].value && rule[k].value.length) {
							href.REPLACE.where+="	main."+k+" "+rule[k].type+" ("+rule[k].value+")";
						} else {
							if (rule[k].type=="in") href.REPLACE.where+="	1=0";
						}
						if (rule[k].or_null) href.REPLACE.where+="\n	)";
						break;
					case "exists": // Potentially unsafe
					case "not exists": // Potentially unsafe
						href.REPLACE.where+=" and\n "+rule[k].type+" ("+rule[k].value+")";
						break;
					case "null":
					case "not null":
						href.REPLACE.where+=" and\n	main."+k+" is "+rule[k].type;
						break;
					case "sql":
						href.REPLACE.where+=" and\n	"+rule[k].value;
						break;
/*					case "or":
						href.REPLACE.where+=" and (\n";
						for (var i=0;i<rule.values.length;i++) {
							href2=this.mk_where(model,rule.values[i],0,pos);
							pos=href2.pos;
							href.REPLACE.where+=href2.REPLACE.where.replace(/^/g,"\t");
						}
						href.REPLACE.where+=")";
						break;*/
					default:
						throw "ModelFactory.prototype.mk_where() - unknown rule type "+rule[k].type+", model="+model+"\nrules:\n"+this.Dumper(rule);
				}
				break;
			default:
				throw "ModelFactory.prototype.mk_where() - unsupported typeof rule["+k+"] - "+typeof rule[k]+", model="+model+"\n";
		}
	}
	if (href.REPLACE.where=="") delete href.REPLACE;
	href.FOOTER+=href.FOOTER2;
	delete href.FOOTER2;
	href.POS=pos;
	return href;
}

/*****************************************************************************/
ModelFactory.prototype.exists=function(model,rule)
{
	var mname=(typeof model == "string")?model:this.model_name(model);
	var fn="_models/"+mname+"/exists";
	if (!this.site.sql.prepared_array[fn]) this.site.sql.prepare(this.site.sql.load(fn),fn);
	var href=this.mk_where(model,rule,null,this.site.sql.prepared_array[fn].length+1);
	var r=this.site.sql.execute_and_fetch("_models/"+mname+"/exists",href);
	return r?true:false;
}

/*****************************************************************************/
ModelFactory.prototype.ids=function(model,rule,idname)
{
	if (!idname) idname="id";
	var ret=[];
	var r2=this.list(model,rule);
	for (var i=0;i<r2.length;i++) ret.push(r2[i][idname]);
	return ret;
}

/*****************************************************************************/
ModelFactory.prototype.count=function(model,rule)
{
	var mname=(typeof model == "string")?model:this.model_name(model);
	var fn="_models/"+mname+"/count";
	if (!this.site.sql.prepared_array[fn]) this.site.sql.prepare(this.site.sql.load(fn),fn);
	var href=this.mk_where(model,rule,null,this.site.sql.prepared_array[fn].length+1);
	delete href.FOOTER;
	var r=this.site.sql.execute_and_fetch_one("_models/"+mname+"/count",href);
	return r.cnt;
}

/*****************************************************************************/
ModelFactory.prototype.list_cached=function(model,rule)
{
	return this.list(model,rule);
}

/*****************************************************************************/
ModelFactory.prototype.list=function(model,rule)
{
	var mname=(typeof model == "string")?model:this.model_name(model);
	var fn="_models/"+mname+"/list";
	if (!this.site.sql.prepared_array[fn]) this.site.sql.prepare(this.site.sql.load(fn),fn);
	var ret=[];
	var href=this.mk_where(model,rule,null,this.site.sql.prepared_array[fn].length+1);
	this.list_helper(model,mname,rule,ret,href,null);
	return ret;
}

/*****************************************************************************/
ModelFactory.prototype.list_hier=function(model,rule,root_id)
{
	if (!root_id) root_id=0;
	var mname=(typeof model == "string")?model:this.model_name(model);
	var fn="_models/"+mname+"/list";
	if (!this.site.sql.prepared_array[fn]) this.site.sql.prepare(this.site.sql.load(fn),fn);
	var ret=[];
	var href;
	if (root_id) {
		var rule1={};
		for (var k in rule) rule1[k]=rule[k];
		rule1.id=root_id;
		href=this.mk_where(model,rule1,null,this.site.sql.prepared_array[fn].length+1);
		this.list_helper(model,mname,rule1,ret,href,0,1);
		href=this.mk_where(model,rule,{root_id:root_id},this.site.sql.prepared_array[fn].length+1);
		href.BIND_ADD[0]=root_id;
		this.list_helper(model,mname,rule,ret,href,1,2);
	} else {
		href=this.mk_where(model,rule,{root_id:root_id},this.site.sql.prepared_array[fn].length+1);
		this.list_helper(model,mname,rule,ret,href,1,1);
	}
	return ret;
}

/*****************************************************************************/
ModelFactory.prototype.list_helper=function(model,mname,rule,ret,href,hier,level)
{
	var r=this.site.sql.execute_and_fetch("_models/"+mname+"/list",href);
	for (var i=0;i<r.length;i++) {
		var a=this.from_object(model,r[i],false);
		ret.push(a);
		switch (hier) {
			case 2:
				var ret2=[];
				href.BIND_ADD[0]=r[i].id;
				this.list_helper(model,mname,rule,ret2,href,hier,level+1);
				a.ChildrenCached=ret2;
				break;
			case 1:
				href.BIND_ADD[0]=r[i].id;
				a.level=level;
				this.list_helper(model,mname,rule,ret,href,hier,level+1);
				break;
		}
	}
}

/*****************************************************************************/
ModelFactory.prototype.del_one=function(model,id)
{
	var mname=(typeof model == "string")?model:this.model_name(model);
	this.site.sql.execute("_models/"+mname+"/delete",{id:id});
}

/*****************************************************************************/
ModelFactory.prototype.del=function(model,rule)
{
	var mname=(typeof model == "string")?model:this.model_name(model);
	var fn="_models/"+mname+"/delete_list";
	if (!this.site.sql.prepared_array[fn]) this.site.sql.prepare(this.site.sql.load(fn),fn);
	var ret=[];
	var href=this.mk_where(model,rule,null,this.site.sql.prepared_array[fn].length+1);
	if (href.REPLACE.where=="") return;
	this.site.sql.execute(fn,href);
}

/*****************************************************************************/
ModelFactory.prototype.get_cached=function(model,rule)
{
	return this.get(model,rule);
}

/*****************************************************************************/
ModelFactory.prototype.get=function(model,rule)
{
	switch (typeof rule) {
		case "string":
		case "number":
			return this.get_by_pk(model,rule);
			break;
		case "object":
			var rule2={};
			for (var k in rule) rule2[k]=rule[k];
			rule2.limit=1;
			var r=this.list(model,rule);
			if (r.length==0) return undefined;
			return r[0];
			break;
	}
	throw("Error - ModelFactory.prototype.get(model="+model+") with param type="+(typeof rule));
}

/*****************************************************************************/
ModelFactory.prototype.get_by_pk=function(model,pk)
{
	var r=this.site.sql.execute_and_fetch_one("_models/"+model+"/get",{id:pk});
	if (!r) return undefined;
	if (!this.site.refs.models[model]) throw "No model "+model+" in this.site.refs.models[]";
	return new this.site.refs.models[model](this.site,r);
}

/*****************************************************************************/
ModelFactory.prototype.from_object=function(model,r,isFromObject)
{
	if (isFromObject!=false) isFromObject=true;
//	var mname=(typeof model == "string")?model:this.model_name(model);
//	if (typeof model == "string") return new this.Model(r,mname,this.site);
	if (!this.site.refs.models[model]) throw "No model "+model+" in this.site.refs.models[]";
	return new this.site.refs.models[model](this.site,r,isFromObject);
}

/*****************************************************************************/
ModelFactory.prototype.model_name=function(model)
{
	return model.ModelName;
//	model.prototype.toString();
}

/*****************************************************************************/
exports.ModelFactory=ModelFactory;

/*****************************************************************************/

